package com.lizk.knowledge.jvm.aqs.unsafe;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 * lzk 输出第一个篇文章
 */
public class UnsafeStudy {
    static Unsafe unsafe;
    static {
        /*
         * 获取Unsafe的时候,需要调用Unsafe的getUnsafe方法,但是这个方法中验证了调用这个方法的类的类加载器必须为null，也就是加载rt.jar的系统加载器.
         *  @CallerSensitive
         *     public static Unsafe getUnsafe() {
         *         Class var0 = Reflection.getCallerClass();
         *         if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
         *             throw new SecurityException("Unsafe");
         *         } else {
         *             return theUnsafe;
         *         }
         *     }
         *
         *     为了能够在自己的代码中使用Unsafe类,使用反射,直接获取"theUnsafe"变量
         */

        try {
            Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
            theUnsafeInstance.setAccessible(true);
            unsafe =  (Unsafe) theUnsafeInstance.get(null);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }


    }

    /**
     *  private static final long valueOffset;
     *
     *     static {
     *         try {
     *             valueOffset = unsafe.objectFieldOffset
     *                 (AtomicInteger.class.getDeclaredField("value"));
     *         } catch (Exception ex) { throw new Error(ex); }
     *     }
     * 在AtomitcInteger类中,有以上一段代码,代码中利用unsafe.objectFieldOffset方法给valueOffset属性设置值.
     * 对象加载到内存以后,可以看做是一块连续的内存,
     * objectFieldOffset这个方法能够获取指定的Field在该对象加载到内存后的一个偏移量。（这里涉及到对象的存储结构,可以展开来讲）
     * unsafe.getInt()或unsafe.getLong()等方法可以直接获取指定对象指定偏移量的内存的值.
     *
     */
    public static void testObjectFieldOffset() throws NoSuchFieldException {
        /*
         * 这个例子可以看出属性定义的顺序和编译后的顺序没有直接关系.
         */
        System.out.println(unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("value")));
        System.out.println(unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("offset")));
        System.out.println(unsafe.objectFieldOffset(MyAtomicInteger.class.getDeclaredField("name")));
    }

    /**
     * 验证利用内存偏移量访问是否比方法调用速度快.
     * 可以看到验证结果,利用偏移量访问变量确实要快很多.
     * @throws NoSuchFieldException
     */
    public static void testOffsetFastThanMethod() throws NoSuchFieldException {
        MyAtomicInteger mai = new MyAtomicInteger("aa",1000,2);
        long begin = System.nanoTime();
        int [] iArray = new int[10000000];

        for (int i = 0 ;i < 10000000 ; i ++){
            iArray[i] =mai.getValue();
        }
        long m = System.nanoTime();
        for (int i = 0 ;i < 10000000 ; i ++){
            iArray[i] =unsafe.getInt(mai, 12L);
        }
        long end = System.nanoTime();

        System.out.println(m - begin);
        System.out.println(end - m);
    }

    public static void testReadByte(MyAtomicInteger mai){
        //通过getByte可以看出数据在内存中具体存储的方式.
        System.out.print(unsafe.getByte(mai,12L));
        System.out.print(unsafe.getByte(mai,13L));
        System.out.print(unsafe.getByte(mai,14L));
        System.out.print(unsafe.getByte(mai,15L));
        System.out.print(" ");
        System.out.print(unsafe.getByte(mai,16L));
        System.out.print(unsafe.getByte(mai,17L));
        System.out.print(unsafe.getByte(mai,18L));
        System.out.print(unsafe.getByte(mai,19L));
        System.out.print(" ");
        System.out.print(unsafe.getByte(mai,20L));
        System.out.print(unsafe.getByte(mai,21L));
        System.out.print(unsafe.getByte(mai,22L));
        System.out.print(unsafe.getByte(mai,23L));
        System.out.println();
    }

    /**
     * 因为unsafe.getInt是Unsafe类直接访问内存地址,理论上访问到的数据不知道是什么类型的数据,而是根据指定的类型读取固定的内存数据的长度.
     * 所以如果对应的偏移量里存储的是一个int值,而用getLong去访问,那会出现什么情况呢？
     */
    public static void testOffsetUnsafe(){
        MyAtomicInteger mai = new MyAtomicInteger("aa",1,4294967297L);
        testReadByte(mai);
        System.out.println(unsafe.getInt(mai, 12L));
        System.out.println(unsafe.getLong(mai, 12L));
        mai = new MyAtomicInteger("aa",1,8589934593L);
        testReadByte(mai);
        System.out.println(unsafe.getInt(mai, 12L));
        System.out.println(unsafe.getLong(mai, 12L));
        mai = new MyAtomicInteger("aa",1,4294967298L);
        testReadByte(mai);
        System.out.println(unsafe.getInt(mai, 12L));
        System.out.println(unsafe.getLong(mai, 12L));
        /*
         * 十进制直接转换二进制
         * 4294967297L == 00000000000000000000000000000001 00000000000000000000000000000001
         * 8589934593L == 00000000000000000000000000000010 00000000000000000000000000000001
         * 4294967298L == 00000000000000000000000000000001 00000000000000000000000000000010
         *
         *
         * 通过unsafe.getByte一个字节一个字节的读取内存可以看到数据的存储是以下结构,
         * 可以看出数据在内存中存储的时候,低位在前(内存低地址位),高位在后(内存高地址位),这是低位优先的存储模式也叫（小端）,这个是由CPU本身架构决定的.
         * 第一次的内存数据为
         * |--------   int  --------------| |-----------------------     long      -------------------------|   正常读取
         * |-----------------------     long      -------------------------|                                    用long读int
         * 00000001000000000000000000000000 00000001000000000000000000000000 00000001000000000000000000000000
         * 因为以long读到的int数据与offset存入的数据一致,所以两个值都是4294967297
         *
         * 第二次的内存数据为
         * |--------   int  --------------| |-----------------------     long      -------------------------|   正常读取
         * |-----------------------     long      -------------------------|                                    用long读int
         * 00000001000000000000000000000000 00000001000000000000000000000000 00000010000000000000000000000000
         * 因为以long读到的int数据与第一次存如的offset出入的二进制数据一致,所以值还是4294967297
         *
         * 第三次的内存数据为
         * |--------   int  --------------| |-----------------------     long      -------------------------|   正常读取
         * |-----------------------     long      -------------------------|                                    用long读int
         * 00000001000000000000000000000000 00000010000000000000000000000000 00000001000000000000000000000000
         * 以long读取到的int数据与第二次出入的offset存入的二进制数据一致,所以取到的值为8589934593,也就是第二次的offset值.
         *
         */


    }


    public static void main(String[] args) throws NoSuchFieldException {
        testOffsetUnsafe();
    }


    public static class MyAtomicInteger{
        String name;
        int value;
        long offset;

        public MyAtomicInteger(String name, int value, long offset) {
            this.name = name;
            this.value = value;
            this.offset = offset;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public long getOffset() {
            return offset;
        }

        public void setOffset(long offset) {
            this.offset = offset;
        }
    }
}
